package controllers

import config.{Global, MyFile}
import org.apache.commons.io.FileUtils
import play.api.data.Form
import play.api.data.Forms._
import play.api.libs.json.{JsObject, Json}
import play.api.mvc.{AbstractController, ControllerComponents}
import tools.{Violin, Volcano}
import utils.ExecCommand

import java.io.File
import java.nio.file.Files
import javax.inject.Inject
import scala.jdk.CollectionConverters._
import scala.math.log10

class FpkmController  @Inject()(cc: ControllerComponents) extends AbstractController(cc) with MyFile{


  def fpkmPage = Action{implicit request=>
    Ok(views.html.fpkm.fpkmPage())
  }

  case class PlotData(group1:  Seq[String], group2: Seq[String])

  val PlotForm = Form(
    mapping(
      "group1" -> seq(text),
      "group2" -> seq(text)
    )(PlotData.apply)(PlotData.unapply)
  )

  def getFpkmResult = Action { implicit request =>
    val data = PlotForm.bindFromRequest.get
    val sampleId =  (data.group1 ++ data.group2).map(_.trim)

    val exp = s"${Global.path}/config/fpkm.txt".readLines.map(_.split("\t")).transpose.map { x =>
      x.head -> x.tail
    }

    val group = data.group1.map { x =>
      x.trim + "\tGroup1"
    } ++ data.group2.map { x =>
      x.trim + "\tGroup2"
    }

    val exps = exp.head +: exp.filter(x => sampleId.contains(x._1))
    val line = exps.map { x => x._1 +: x._2 }.transpose.map(_.mkString("\t"))
    val tmpDir = Files.createTempDirectory("tmpDir").toString
    FileUtils.writeLines(s"$tmpDir/group.txt".toFile, group.toBuffer.asJava)
    FileUtils.writeLines(s"$tmpDir/matrix.txt".toFile, line.asJava)

    println(tmpDir)
    val vio = Violin.Run(tmpDir)
    val heat = cHeatMap(line.mkString("\n"), tmpDir)
    val hclust = plotCluster(line.mkString("\n"), tmpDir)
    val vol = Volcano.Run(tmpDir)

    val state = (vio \ "state").as[Int] + (heat \ "state").as[Int] + (hclust \ "state").as[Int] + (vol \ "state").as[Int]
    Ok(
      if (state == 0) {
        Json.obj("heatmap" -> (heat \ "json").as[Seq[JsObject]], "hclust" -> (hclust \ "div").as[String],
          "boxPlot" -> (vio \ "boxPlot").as[JsObject], "vol" -> (vol \ "vol").as[String])
      } else {
        val msg = getMsg(vio) + getMsg(heat) + getMsg(hclust) + getMsg(vol)
        println(msg)
        Json.obj("valid" -> "false", "msg" -> msg)
      }
    )
  }

  def getMsg(json: JsObject) = {
    (json \ "msg").asOpt[String].getOrElse("")
  }


  def cHeatMap(matrix: String, tmpDir: String): JsObject = {
    val dealFile = s"$tmpDir/deal.txt".toFile
    getMatrixFile(matrix, dealFile)
    val rCommand = s"Rscript  --restore --no-save ${Global.toolsPath}/cCoefficient.R"
    val exec = new ExecCommand()
    exec.exect(Array(rCommand), tmpDir)
    println(rCommand)
    if (exec.isSuccess) {
      val lines = s"$tmpDir/out.txt".readLines
      val colNames = lines.head.split("\t")
      val rowNames = lines.tail.map(_.split("\t")(0))
      val expressions = for (i <- colNames.indices; j <- rowNames.indices) yield {
        val value = lines(i + 1).split("\t")(j + 1).toDouble
        Array(i, j, value)
      }
      val max = expressions.toArray.map(x => x(2)).max
      val min = expressions.toArray.map(x => x(2)).min
      Json.obj("json" -> Seq(Json.obj("expression" -> expressions, "treatment" -> colNames, "gt" -> rowNames,
        "max" -> max, "min" -> min)), "state" -> 0)
    } else {
      Json.obj("valid" -> "false", "msg" -> exec.getOutStr, "state" -> 1)
    }
  }

  def plotCluster(matrix: String, tmpDir: String): JsObject = {
    val dealFile = s"$tmpDir/deal.txt".toFile
    FileUtils.writeStringToFile(dealFile, matrix)
    val pyCommand = s"python ${Global.path}/tools/dendrogram.py"
    val exec = new ExecCommand()
    exec.exect(Array(pyCommand), tmpDir)
    if (exec.isSuccess) {
      val divStr = s"$tmpDir/div.txt".readFileToString
      Json.obj("div" -> divStr, "state" -> 0)
    } else {
      Json.obj("valid" -> "false", "msg" -> exec.getErrStr, "state" -> 1)
    }
  }

  def getMatrixFile(str: String, file: File) = {
    val matrix = str.split("\n").map(_.split("\t"))
    val row = matrix.zipWithIndex.map { case (line, i) =>
      if (i > 0) {
        line.zipWithIndex.map { case (col, j) =>
          if (j > 0) {
            log2(col.trim.toDouble).toString
          } else {
            col
          }
        }.mkString("\t")
      } else {
        line.mkString("\t")
      }
    }.toBuffer
    FileUtils.writeLines(file, row.asJava)
  }

  //log10(x):10的多少次方等于x
  def log2(x: Double): Double = log10(x + 1) / log10(2.0)

}
